package comet

import (
	"errors"
	"fmt"
	"io"
	"runtime/debug"
	"sync"
	"time"

	"gddgame.cc/galaxy/satellite"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

var (
	TransportError = errors.New("transport is error")
	ProtocolError  = errors.New("protocal is error")
	clientPool     = sync.Pool{}
)

func init() {
	clientPool.New = func() interface{} {
		c := &clientInline{}
		c.init()
		return c
	}
}

// 消息通道，链接状态
type clientInline struct {
	def.Logger
	def.Store

	sid           string
	authorization string
	server        *Server
	gateway       *satellite.Gateway
	pp            *satellite.PingPongServer
	allow         *satellite.AllowMulti

	close  chan struct{}
	closed bool
	flow   bool

	transportNotice []int64

	app *AppPacket

	iss []*Socket

	authority interface{}
}

func newServerClient(e *Server, g *satellite.Gateway, sid string) *clientInline {
	c := clientPool.Get().(*clientInline)
	c.sid = sid
	c.server = e
	// 设定log标识
	c.Store = e.cache.Store(sid)
	c.Logger = e.logger.LoggerWrap(e.options.TrackId, sid)
	c.gateway = g
	c.allow = satellite.NewAllowMulti(e.transportTypes)
	c.transportNotice = make([]int64, c.server.transportTypes.Len())
	c.iss = make([]*Socket, len(e.interfaces))
	c.closed = false
	c.flow = false
	return c
}
func (c *clientInline) init() {
	c.close = make(chan struct{}, 1)
	c.pp = satellite.NewPPServer(5, c)
}
func (c *clientInline) Release() {
	c.server.cache.Clean(c.sid)
	c.allow = nil
	c.transportNotice = make([]int64, c.server.transportTypes.Len())
	c.iss = nil
	c.server = nil
	c.gateway = nil
	c.app = nil
	c.Store = nil

	c.init()
	clientPool.Put(c)
}

func (c *clientInline) recover(err interface{}) {
	exception, ok := err.(error)
	if c.server.debug {
		debug.PrintStack()
	}
	if ok {
		c.onError(exception)
	} else {
		c.onError(errors.New(err.(string)))
	}
}

func (c *clientInline) SendError(err error) {
	c.Error("[ Comet ] Server Error:", err)
	exception, ok := err.(def.Exception)
	message := err.Error()
	if ok {
		message = exception.Message()
	}
	c.SendByte(0, P_ERROR, c.server.transportTypes.Search(satellite.SafeTransport), false, []byte(message))
}

func (c *clientInline) SendPong(bytes []byte) {
	c.SendByte(0, P_PONG, c.server.transportTypes.Search(satellite.SafeTransport), false, bytes)
}

func (c *clientInline) SendBasePong(transportType satellite.TransportType) {
	c.SendByte(0, P_PONG, c.server.transportTypes.Search(transportType), false, nil)
}

func (c *clientInline) Send(no DN, mt MT, transportIndex int, compress bool, f func(writer io.Writer) error) {
	if c.closed {
		return
	}
	conn, err := c.allow.SelectConn(transportIndex)
	if err != nil {
		return
	}
	//fmt.Printf("send %s:%s->%s\n", c.sid, no, mt)
	err = c.gateway.Write(byte(no<<4&0xff)|byte(mt), conn, compress, f)
	if err != nil {
		c.Errorf("[ Comet ] Client send:%s", err)
	}
}

func (c *clientInline) SendData(no DN, mt MT, transportIndex int, compress bool, data satellite.DataPacket) {
	c.Send(no, mt, transportIndex, compress, data.Encode)
}

func (c *clientInline) SendByte(no DN, mt MT, transportIndex int, compress bool, data []byte) {
	c.Send(no, mt, transportIndex, compress, func(writer io.Writer) error {
		//fmt.Println(t, string(data))
		if data != nil {
			_, err := writer.Write(data)
			return err
		} else {
			return nil
		}
	})
}

func (c *clientInline) Close() {
	c.SendByte(0, P_CLOSE, 0, false, []byte(c.sid))
	c.destroy("forced server close")
}

func (c *clientInline) Disconnect(s *Socket) error {
	if c.iss[s.no] == nil {
		return fmt.Errorf("no socket for interface %b", s.no)
	}
	c.iss[s.no] = nil
	return nil
}

func (c *clientInline) App() *AppPacket {
	return c.app
}

func (c *clientInline) Auth() interface{} {
	return c.authority
}

func (c *clientInline) ID() string {
	return c.sid
}

func (c *clientInline) Connected() bool {
	return !c.closed
}

func (c *clientInline) onOpen(transportIndex int, conn satellite.Conn) {
	c.allow.Connect(transportIndex, conn)
	c.SendByte(0, P_OPEN, transportIndex, false, []byte(c.sid))
}

func (c *clientInline) onReopen(transportIndex int, conn satellite.Conn) {
	c.allow.Connect(transportIndex, conn)
	c.SendByte(0, P_REOPEN, transportIndex, false, []byte(c.sid))
}

func (c *clientInline) OnClose(conn satellite.Conn) {
	c.onClose(c.server.transportTypes.Search(conn.Transport().Type()))
}
func (c *clientInline) onClose(transportIndex int) {
	index := c.allow.OnClose(transportIndex)
	if index == -1 {
		c.destroy("client close")
	}
}

func (c *clientInline) onProtocol(transportIndex int, destType satellite.TransportType) {
	// 控制发送协议数据的频率

	t := c.transportNotice[transportIndex]
	if t > 0 {
		if time.Now().Unix()-t < 60 {
			return
		}
	}
	var b string
	switch destType {
	case satellite.SpeedTransport:
		b = string(satellite.SpeedTransport) + "://" + c.server.options.PublishUdp
	case satellite.DefaultTransport:
		b = string(satellite.DefaultTransport) + "://" + c.server.options.PublishTcp
	}
	c.transportNotice[transportIndex] = time.Now().Unix()
	c.Debugf("client: %s send protocol:%#v", c.sid, b)
	// 已连接的协议，会在下次链接时自动关闭
	c.SendByte(0, P_PROTOCOL, transportIndex, false, []byte(b))
}

func (c *clientInline) onApplication(message satellite.Message) {
	//defer func() {
	//	if err := recover(); err != nil {
	//		c.recover(err)
	//	}
	//}()
	if c.app != nil {
		c.Debugf("[ Comet ] application is exist:%s", c.sid)
	}
	p := GetApp()
	message.ReadData(p)
	c.Debugf("[ Comet ] client: %s accept application %#v", c.sid, p)
	c.app = p
}

func (c *clientInline) onAuth(transportIndex int, authorization string, no byte) {
	c.authorization = authorization
	if c.app == nil || c.server.authHandle == nil {
		return
	}
	authority, err := c.server.authHandle(c, c.app, authorization, c.authority)
	if err != nil {
		c.Errorln(err.Error())
		b := append([]byte{no}, utils.ToBytes(err.Error())...)
		c.SendByte(0, P_AUTHERRROR, transportIndex, false, b)
	} else {
		c.authority = authority
		if c.iss[no] != nil && c.iss[no].authHandle != nil {
			c.iss[no].authHandle(c.authority)
		}
		c.SendByte(0, P_AUTH, transportIndex, false, []byte{no})
	}
}

func (c *clientInline) OnAccept(conn satellite.Conn, code byte, message satellite.Message) error {
	//defer func() {
	//	if err := recover(); err != nil {
	//		c.recover(err)
	//	}
	//}()
	if c.closed {
		return nil
	}
	t := MT(code & 0xf)
	n := DN(code >> 4)
	transportType := conn.Transport().Type()
	if n == SYSTEM_NO {
		index := c.server.transportTypes.Search(transportType)
		if index < 0 {
			c.Debugf("[ Comet ] client: %s %s is error, close", c.sid, transportType)
			c.onClose(index)
			return TransportError
		}
		if !c.flow {
			switch t {
			case P_OPEN:
				// 已经finish，无需再次finish
				c.Debugf("[ Comet ] client: %s accept open：%s", c.sid, transportType)
				c.onOpen(index, conn)
			case P_REOPEN:
				c.Debugf("[ Comet ] client: %s accept reopen：%s", c.sid, transportType)
				c.onReopen(index, conn)
			case P_CLOSE:
				c.Debugf("[ Comet ] client: %s accept close：%s in flow", c.sid, transportType)
				c.onClose(index)
			default:
				c.Debugf("[ Comet ] client: %s accept %s：%s", c.sid, t, transportType)
				// 流程错误
				c.Close()
				return ProtocolError
			}
			c.flow = true
		} else {
			switch t {
			case P_REOPEN:
				c.Debugf("[ Comet ] client: %s accept reopen：%s", c.sid, transportType)
				c.onReopen(index, conn)
			case P_CLOSE:
				c.Debugf("[ Comet ] client: %s accept close：%s out flow", c.sid, transportType)
				c.onClose(index)
			case P_PING:
				//c.Debugf("[ Comet ] client: %s accept ping", index)
				if transportType == satellite.SafeTransport {
					c.pp.OnPing()
				} else {
					c.SendBasePong(transportType)
				}
			case P_PONG:
				//c.Debugf("[ Comet ] client: %s accept pong", index)
				c.pp.OnPong(message)
			case P_PROTOCOL:
				b := satellite.TransportType(message.ReadBytes())
				c.Debugf("[ Comet ] client: %s accept protocol %s", c.ID(), index, b)
				go c.onProtocol(index, b)
			case P_APPLICATION:
				c.onApplication(message)
			case P_AUTH:
				token := message.ReadBytes()
				c.Debugf("[ Comet ] client: %s accept token %#v", c.ID())
				c.onAuth(index, utils.ToString(token[1:]), token[0])
			default:
				return ProtocolError
			}
		}
	} else {
		// 判断下长度
		//c.Debugf("accept:%s, %s", c.sid, transportType)
		if int(n) >= len(c.iss) {
			c.SendError(fmt.Errorf("No(%d) is empty", n))
			return ProtocolError
		}
		s := c.iss[n]
		if s == nil {
			i := c.server.interfaces[n]
			if i == nil {
				c.SendError(fmt.Errorf("No(%d) is empty", n))
				return ProtocolError
			}
			s = newSocket(n, i, c)
			s.Connect()
			if !s.connected {
				return ProtocolError
			}
			c.iss[n] = s
			if s.authHandle != nil && c.authority != nil {
				s.authHandle(c.authority)
			}
		}
		// c.logger.Debugf("accept %#v", t)
		return s.onAccept(t, message)
	}
	return nil
}

func (c *clientInline) onError(err error) {
	debug.PrintStack()
	c.Errorf("Client error:%s", err)
	//c.destroy("client data error")
}

func (c *clientInline) destroy(reason string) {
	if c.closed {
		return
	}
	c.closed = true
	close(c.close)
	c.Debugf("[ Comet ] Client :%s destroy %s", c.sid, reason)
	c.allow.Close()
	for k, _ := range c.iss {
		if c.iss[k] != nil {
			c.iss[k].Disconnect()
		}
	}
	c.gateway.Disconnect(c.sid)
	c.app.Release()
	c.Release()
}
